#include "mesh.h"
#include "helper.h"
#include <string>

Mesh::Mesh() {
	mesh = 0;
	vertexLayout = 0;
	effect = 0;
	effectTechnique = 0;
	WorldVariable = 0;
	ViewVariable = 0;
	ProjectionVariable = 0;
	pTextureRV = 0;
	pCameraPositionVariable = 0;
	pAmbientVariable = 0;
	pDiffuseVariable = 0;
	pSpecularVariable = 0;
	pSpecularPowerVariable = 0;
	objloader = 0;

}

Mesh::~Mesh() {
}

bool Mesh::InitialiseModels(ID3D10Device *device) {
	// shaders
	if (!InitialiseShader(device))
		return false;

	objloader = new ObjLoader;
	if (objloader == NULL)
		return false;

	if (!objloader->InitialiseModel(L"media\\models\\skybox", L"dragon.obj"))
		return false;

	// create scene objects
	if (!InitialiseGeometry(device))
		return false;

	if (!LoadTextures(device))
		return false;

	return true;
}

void Mesh::Cleanup() {
	if (objloader != NULL) {
		delete objloader;
		objloader = 0;
	}

	ID3D10ShaderResourceView* tex = 0;
	for (UINT i = 0; i < textures.size(); i++) {
		tex = textures[i];
		ID3D10Resource *pRes;
		tex->GetResource(&pRes);
		Release(pRes);
		Release(tex);
	}

	Release(mesh);
	Release(effect);

	pTextureRV = NULL;
	effectTechnique = NULL;
	WorldVariable = NULL;
	ViewVariable = NULL;
	ProjectionVariable = NULL;
}

void Mesh::Render(ID3D10Device *device) {
	device->IASetInputLayout(vertexLayout);
	D3D10_TECHNIQUE_DESC techDesc;
	effectTechnique->GetDesc(&techDesc);

	for (UINT i = 0; i < objloader->pod_materials.size(); i++) {

		pod::Material *temp = objloader->pod_materials[i];
		pAmbientVariable->SetFloatVector(temp->ambient);
		pDiffuseVariable->SetFloatVector(temp->diffuse);
		pSpecularVariable->SetFloatVector(temp->specular);
		pSpecularPowerVariable->SetFloat(temp->specularPower);
		illumVariable->SetInt(temp->illum);
		// TODO use a different technique
		if (temp->isTextured)
			pTextureRV->SetResource(textures[temp->resourceIndex]);

		for (UINT p = 0; p < techDesc.Passes; p++) {

			effectTechnique->GetPassByIndex(p)->Apply(0);
			mesh->DrawSubset(i);
		}
	}
}

void Mesh::SetShaderVars(D3DXMATRIX World, D3DXMATRIX View,
		D3DXMATRIX Projection) {
	WorldVariable->SetMatrix(World);
	ViewVariable->SetMatrix(View);
	ProjectionVariable->SetMatrix(Projection);
	// TODO update camera position
	pCameraPositionVariable->SetFloatVector(D3DXVECTOR3(0.0f, 0.0f, -10.0f));
}

bool Mesh::InitialiseGeometry(ID3D10Device *device) {

	// create a mesh
	HRESULT hr = D3DX10CreateMesh(device, ptn::layout, ptn::numElements,
			ptn::layout[0].SemanticName, objloader->pod_vertices.size(),
			objloader->pod_attributes.size(), D3DX10_MESH_32_BIT, &mesh);

	if (FAILED(hr))
		return Error(L"Could not create a mesh");

	mesh->SetVertexData(0, &objloader->pod_vertices[0]);
	mesh->SetIndexData(&objloader->pod_indices[0],
			objloader->pod_indices.size());
	mesh->SetAttributeData(&objloader->pod_attributes[0]);
	mesh->GenerateAdjacencyAndPointReps(1e-6f);
	mesh->Optimize(D3DX10_MESHOPT_ATTR_SORT | D3DX10_MESHOPT_VERTEX_CACHE,
			NULL, NULL);
	mesh->CommitToDevice();

	return true;
}

bool Mesh::InitialiseShader(ID3D10Device *device) {
	HRESULT hr = S_OK;
	ID3D10Blob *pErrors = NULL;

	hr = D3DX10CreateEffectFromFile(
			L"D:\\incoming\\Workspace\\Shader Labs\\shader.fx", NULL, NULL,
			"fx_4_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, device, 0, 0, &effect,
			&pErrors, &hr);

	if (FAILED(hr)) {
		if (pErrors) {
			LPVOID compileErrors = pErrors->GetBufferPointer();
			using namespace std;
			string strErr = (const char*) compileErrors;
			wstring wcstrErr(strErr.begin(), strErr.end());
			return Error((WCHAR*) wcstrErr.c_str(), L"Shader Compile Errors.");
		} else {
			return Error(L"Shader file missing!");
		}
	}

	effectTechnique = effect->GetTechniqueByName("Default");
	if (effectTechnique == NULL)
		return Error(L"Could not acquire the technique", L"Shader Error!");

	// obtain shader variables
	ObtainShaderVars();

	// create input layout
	if (!CreateInputLayout(device))
		return false;

	return true;
}

bool Mesh::CreateInputLayout(ID3D10Device *device) {
	using namespace ptn;
	D3D10_PASS_DESC pDesc;
	effectTechnique->GetPassByIndex(0)->GetDesc(&pDesc);

	HRESULT hr = device->CreateInputLayout(layout, numElements,
			pDesc.pIAInputSignature, pDesc.IAInputSignatureSize, &vertexLayout);

	if (FAILED(hr))
		return Error(L"Could not create input layout");

	return true;
}

void Mesh::ObtainShaderVars() {
	WorldVariable = effect->GetVariableByName("World")->AsMatrix();
	ViewVariable = effect->GetVariableByName("View")->AsMatrix();
	ProjectionVariable = effect->GetVariableByName("Projection")->AsMatrix();
	pAmbientVariable = effect->GetVariableByName("Ka")->AsVector();
	pDiffuseVariable = effect->GetVariableByName("Kd")->AsVector();
	pSpecularVariable = effect->GetVariableByName("Ks")->AsVector();
	pTextureRV = effect->GetVariableByName("DiffuseMap")->AsShaderResource();
	pCameraPositionVariable
			= effect->GetVariableByName("CameraPosition")->AsVector();
	pSpecularPowerVariable
			= effect->GetVariableByName("SpecularPower")->AsScalar();
	illumVariable = effect->GetVariableByName("illum")->AsScalar();
	isTexturedVariable = effect->GetVariableByName("textured")->AsScalar();

}

bool Mesh::LoadTextures(ID3D10Device *device) {
	HRESULT hr = S_OK;
	WCHAR name[MAX_PATH] = { 0 };
	for (UINT i = 0; i < objloader->pod_materials.size(); ++i) {
		ID3D10ShaderResourceView* tex = 0;
		pod::Material *material = objloader->pod_materials[i];
		if (material->isTextured) {
			hr |= D3DX10CreateShaderResourceViewFromFile(device,
					material->diffuseMap, NULL, NULL, &tex, NULL);
			if (FAILED(hr)) {
				return Error(L"Some of the mesh textures could not be loaded",
						name);
			}

			material->resourceIndex = textures.size();
			textures.push_back(tex);
		}
	}

	return true;
}
